import path from 'path';
import webpack from 'webpack';
import { trimStart } from 'lodash';
import TerserPlugin from 'terser-webpack-plugin';
import OptimizeCssAssetsPlugin from 'optimize-css-assets-webpack-plugin';
import CircularDependencyPlugin from 'circular-dependency-plugin';
//import AssetsPlugin from 'assets-webpack-plugin';
import MiniCssExtractPlugin from 'mini-css-extract-plugin';
//import { outputFile } from 'fs-extra';
import CopyPlugin from 'copy-webpack-plugin';
import WebpackBar from 'webpackbar';

import { PageMapping, PageResourceMapping, ResourceNotBuildInMapping } from '../config';
import getConfig from '../utils/config.util';

import AllConst, { WebpackConfig } from '../constants';
import { CacheGroupAssetsWebpackPlugin } from '../../plugins';
import { CacheGroupAsset } from '../../plugins/webpack/cache-group-assets-webpack-plugin';

// refer to: https://jishuin.proginn.com/p/763bfbd64177

const { PROJECT_PATH, STATIC_PATH_DEV, STATIC_PATH_PRD, JSDELIVR_STATIC_PATH_PRD } =
    AllConst.ProjectConfig;

function getEntry(): PageMapping {
    const pageConfig = getConfig('page.entry.ts', './tools/scripts/config') as PageMapping;

    const pageMapping: PageMapping = {};
    Object.keys(pageConfig).forEach((key) => {
        const value = pageConfig[key];

        if (value?.trim()?.length >= 0) {
            pageMapping[key] = path.resolve(PROJECT_PATH, pageConfig[key]);
        }
    });

    return pageMapping;
}

const chunks = getEntry();

export function getCommonWebpackConfig(isDev: boolean) {
    const assetsFileName = 'resources.mapping.json';
    const assetsBasicPath = path.resolve(
        PROJECT_PATH,
        `./dist/${isDev ? 'development' : 'publish'}`,
    );
    const serverAssetsPath = path.resolve(assetsBasicPath, `/conf`);
    const clientAssetsPath = path.resolve(assetsBasicPath, `server/conf`);

    const conf: webpack.Configuration = {
        target: 'web',
        mode: 'production',
        entry: chunks,
        module: {
            rules: [
                {
                    oneOf: WebpackConfig.getCommonModuleOneOfRules(isDev),
                },
            ],
        },
        resolve: {
            extensions: ['.tsx', '.ts', '.js', '.json'],
            // refactor here, use tsconfig
            alias: {
                '@libs': path.resolve(PROJECT_PATH, './src/libs'),
                '@static': path.resolve(PROJECT_PATH, './static'),
                '@config': path.resolve(PROJECT_PATH, './config'),
                '@': path.resolve(PROJECT_PATH, './src'),
            },
        },
        output: {
            path: path.resolve(PROJECT_PATH, `./dist/${isDev ? 'development' : 'publish'}/static`),
            filename: `scripts/[name].${isDev ? '' : '[contenthash].'}js`,
        },
        plugins: [
            ...WebpackConfig.fixedPlugins,
            new MiniCssExtractPlugin({
                filename: 'styles/[name].[contenthash:8].css',
                chunkFilename: 'styles/[name].[contenthash:8].css',
                ignoreOrder: false,
            }),
            new CopyPlugin({
                patterns: [
                    {
                        context: path.resolve(PROJECT_PATH, './static/assets'),
                        from: '**/*',
                        to: path.resolve(
                            PROJECT_PATH,
                            `./dist/${isDev ? 'development' : 'publish'}/static/assets`,
                        ),
                        toType: 'dir',
                    },
                ],
                // copyUnmodified: true,
            }),
            new WebpackBar({
                name: isDev ? 'Starting' : 'Packaging',
                color: '#fa8c16',
            }),
            new CircularDependencyPlugin({
                exclude: /node_modules/,
                include: /src/,
                failOnError: true,
                allowAsyncCycles: false,
                cwd: process.cwd(),
            }),
            new CacheGroupAssetsWebpackPlugin({
                path: clientAssetsPath,
                filename: assetsFileName,
                processOutputFn: (chunkMapping) => {
                    const scripts = {};

                    const devPath = STATIC_PATH_DEV?.endsWith('/')
                        ? STATIC_PATH_DEV
                        : STATIC_PATH_DEV + '/';
                    const prdPath = JSDELIVR_STATIC_PATH_PRD?.endsWith('/')
                        ? JSDELIVR_STATIC_PATH_PRD
                        : JSDELIVR_STATIC_PATH_PRD + '/';
                    const resourcePrefixPath = isDev ? devPath : prdPath;

                    const pageResourceConfig = getConfig(
                        'page.resource.ts',
                        './tools/scripts/config',
                    ) as PageResourceMapping;
                    for (let chunk in chunkMapping) {
                        const assetList = chunkMapping[chunk];

                        const allJsFiles = getResourcesByExtension(
                            assetList,
                            'js',
                            resourcePrefixPath,
                        );

                        const allCssFiles = getResourcesByExtension(
                            assetList,
                            'css',
                            resourcePrefixPath,
                        );

                        scripts[chunk] = { js: allJsFiles, css: allCssFiles };
                    }

                    const assetsData = `${JSON.stringify(scripts, null, 2)}`;

                    return assetsData;
                },
            }),
        ],
        optimization: {
            minimize: !isDev,
            minimizer: [
                !isDev &&
                    new TerserPlugin({
                        extractComments: false,
                        terserOptions: {
                            compress: { pure_funcs: ['console.log'] },
                        },
                    }),
                !isDev && new OptimizeCssAssetsPlugin(),
            ].filter(Boolean),

            splitChunks: {
                chunks: 'all',
                minSize: 20000,
                //maxSize: 20000,
                minChunks: 1,
                maxAsyncRequests: 7,
                maxInitialRequests: 4,
                //name: true,
                automaticNameDelimiter: '~',
                cacheGroups: {
                    react: {
                        test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
                        priority: 0,
                        name: 'react',
                    },
                    lodash: {
                        test: /[\\/]node_modules[\\/](lodash|lodash-es)[\\/]/,
                        priority: -5,
                        name: 'lodash',
                    },
                    commonVendors: {
                        test: /[\\/]node_modules[\\/](?!react|react-dom|lodash|loadsh-es)/,
                        // minChunks: 2,
                        priority: -10,
                        name: 'common-vendors',
                    },
                    CommonLibs: {
                        test: /[\\/]src[\\/]libs[\\/]/,
                        // minChunks: 2,
                        priority: -15,
                        name: 'common-libs',
                    },
                    // default: {
                    //     minChunks: 2,
                    //     priority: -20,
                    //     chunks: 'initial',
                    //     //reuseExistingChunk: true,
                    // },
                },
            },
        },
    };

    return conf;
}

function getResourcesByExtension(
    assets: CacheGroupAsset[],
    ext: string,
    pathPrefix?: string,
): string[] {
    return assets?.reduce(
        (pre, curAsset) =>
            pre.concat(
                curAsset.files
                    ?.filter((file) => file.endsWith(`.${trimStart(ext, '.')}`))
                    ?.map((file) => `${pathPrefix ?? ''}${file}`),
            ),
        [],
    );
}
